package apibasej.basic.misc;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParseException;
import java.text.SimpleDateFormat;

import apibasej.basic.config.ConfigManager;
import apibasej.basic.exception.APIBaseRuntimeException;
import apibasej.basic.exception.InvalidTypeException;
import apibasej.basic.misc.standalone.UtilPrimitives;
import apibasej.basic.misc.standalone.UtilString;


public class UtilStrFormat extends ConfigManager{

	/*
	public static void main(String[] args) {
		//System.out.println(new Double("12."));
		System.out.println(Double.MAX_VALUE);
		System.out.println(new Double("1234567891234567.0"));
		DecimalFormat df = new DecimalFormat();

		//df.getDecimalFormatSymbols().setDecimalSeparator('_');//não funciona! tem que criar um novo DecimalFormatSymbols e setar!
		//df.getDecimalFormatSymbols().setGroupingSeparator('*');//não funciona! tem que criar um novo DecimalFormatSymbols e setar!
		DecimalFormatSymbols ds = new DecimalFormatSymbols();
		ds.setDecimalSeparator('_');
		ds.setGroupingSeparator('#');
		df.setDecimalFormatSymbols(ds);
		
		df.setMinimumFractionDigits(3);
		df.setGroupingSize(3);
		System.out.println(df.format(new Double("1234567891234567.0")));
	}*/

	@SuppressWarnings("unchecked")
	public <T> T parse(String value, Class<T> typeObj){
		return (T)parseUnchecked(value, typeObj);
	}
	
	@SuppressWarnings("unchecked")
	public <T> T parseUnchecked(String value, Class<?> typeObj){
		
		// verifica todos os tipos para converter para o valor certo
		
		if(value==null || value.length()==0) return null;
		if(typeObj==null) return (T)value;
		
		if(typeObj.isPrimitive()){
			typeObj = UtilPrimitives.wrap(typeObj);
		}
		
		if(String.class.equals(typeObj)) return (T)value;
		
		else if(Integer.class.equals(typeObj)) return (T)new Integer(prepareStrToInt(value));
		else if(Long.class.equals(typeObj)) return (T)new Long(prepareStrToInt(value));
		else if(Short.class.equals(typeObj)) return (T)new Short(prepareStrToInt(value));
		else if(Byte.class.equals(typeObj)) return (T)new Byte(prepareStrToInt(value));
		else if(Long.class.equals(typeObj)) return (T)new Long(prepareStrToInt(value));
		else if(BigInteger.class.equals(typeObj)) return (T)new BigInteger(prepareStrToInt(value));
		
		else if(Boolean.class.equals(typeObj)) return (T)getBoolean(value);
		
		else if(Double.class.equals(typeObj)) return (T)new Double(prepareStrToDecimal(value));
		else if(Float.class.equals(typeObj)) return (T)new Float(prepareStrToDecimal(value));
		else if(BigDecimal.class.equals(typeObj)) return (T)new BigDecimal(prepareStrToDecimal(value));
		
		else if(java.util.Date.class.equals(typeObj)) return (T)new java.util.Date(prepareStrToTimeLong(value,typeObj));
		else if(java.sql.Date.class.equals(typeObj)) return (T)new java.sql.Date(prepareStrToTimeLong(value,typeObj));
		else if(java.sql.Time.class.equals(typeObj)) return (T)new java.sql.Time(prepareStrToTimeLong(value,typeObj));
		else if(java.sql.Timestamp.class.equals(typeObj)) return (T)new java.sql.Timestamp(prepareStrToTimeLong(value,typeObj));
		
		else throw new InvalidTypeException("Unexpected type: "+typeObj.getName());
		
		// !!!! ver outros tipos !!!!
	}
	
	public String format(Object obj){
		return format(obj,null);
	}
	
	public String format(Object obj, Class<?> classForPatternFormat){// , Class<?> typeObj não precisa do typeObj pois o value já deve ser dotipo do obj
		if(obj==null) {
			return null;
		}
		if(obj instanceof String){
			return (String)obj;//obj.toString();
		}else if(obj instanceof Number){
			
			if( obj instanceof Double || obj instanceof Float ){// se é decimal
				return formatDecimal(obj);
			}else{// é inteiro
				return UtilString.completeZerosLeft(obj.toString(), getProp(MINIMUM_INTEGER_DIGITS));
			}
			
		}else if(obj instanceof java.util.Date){
			if(classForPatternFormat==null){
				classForPatternFormat = obj.getClass();
			}
			return new SimpleDateFormat(getPattern(classForPatternFormat)).format( (java.util.Date)obj );
			
		}else if(obj instanceof Boolean){
			if(classForPatternFormat==null){
				classForPatternFormat = obj.getClass();
			}
			String p = getPattern(classForPatternFormat);
			if(p!=null && p.indexOf(SEPARATOR_PATTERN_BOOLEAN)>0){ // o pattern para boolean será true/false (Sim/Não, V/F ...) 
				String[] ps = p.split(SEPARATOR_PATTERN_BOOLEAN);
				if( (Boolean)obj ) return ps[0];
				else return ps[1];
			}else{
				return obj.toString();
			}
		}else{
			throw new InvalidTypeException("Unexpected type: "+obj.getClass().getName());
		}
	}
	
	
	private String formatDecimal(Object obj){
		DecimalFormat df = new DecimalFormat();
		
		//df.getDecimalFormatSymbols().setDecimalSeparator(getProp(DECIMAL_SEPARATOR));//não funciona! tem que criar um novo DecimalFormatSymbols e setar!
		//df.getDecimalFormatSymbols().setGroupingSeparator(getProp(GROUPING_SEPARATOR));//não funciona! tem que criar um novo DecimalFormatSymbols e setar!
		DecimalFormatSymbols ds = new DecimalFormatSymbols();
		ds.setDecimalSeparator(getProp(DECIMAL_SEPARATOR));
		ds.setGroupingSeparator(getProp(GROUPING_SEPARATOR));
		df.setDecimalFormatSymbols(ds);
		
		df.setMinimumFractionDigits(getProp(MINIMUM_FRACTION_DIGITS));
		df.setMaximumFractionDigits(getProp(MAXIMUM_FRACTION_DIGITS));
		df.setMinimumIntegerDigits(getProp(MINIMUM_INTEGER_DIGITS));
		df.setGroupingSize(getProp(GROUPING_SIZE));
		
		//o RoundingMode.HALF__EVEN é o default do DecimalFormat
		//usar HALF_UP para 0,005 mostrar 0,01 (HALF__EVEN mostra 0,00)
		//para setar os valores double nos campos deve usar o arredondamento (HALF_UP), mas para formatar campos ao digitar deve cortar o final (DOWN)
		df.setRoundingMode(getRoundingModeDecimal());//para cortar as casas que não serão exibidas, sem arredondar
		return df.format(obj);
	}
	
	private RoundingMode roundingModeDecimal = RoundingMode.HALF_UP; //defualt !!!!
	public void setRoundingModeDecimal(RoundingMode roundingModeDecimal) {
		this.roundingModeDecimal = roundingModeDecimal;
	}
	public RoundingMode getRoundingModeDecimal() {
		if(roundingModeDecimal==null){
			return RoundingMode.HALF_UP;
		}else{
			return roundingModeDecimal;
		}
	}
	
	public String prepareStrToInt(String s){
		if(s==null){
			throw new NullPointerException("prepareStrToInt(s) não deve receber null!");//não deve ocorrer
		}
		StringBuilder sb = new StringBuilder();
		for(char c : s.toCharArray()) {
			if(Character.isDigit(c) || c=='-') {
				sb.append(c);
			}else if(getProp(DECIMAL_SEPARATOR).equals(c)){
				// se for int e chegar nas casas decimais, descarta o resto
				break;
			}
		}
		if(sb.length()==0 || (sb.length()==1 && sb.charAt(0)=='-') ) {
			return "0";
		}
		return sb.toString();
	}
	public String prepareStrToDecimal(String s){
		StringBuilder sb = new StringBuilder();
		int posLastDecSep = -1;
		for(char c : s.toCharArray()) {
			if(Character.isDigit(c)) {
				sb.append(c);
			}else if(c=='-') {
				if(sb.length()==0 || sb.charAt(0)!='-'){
					sb.insert(0,'-');
				}
			}else if(c==getProp(DECIMAL_SEPARATOR) || c=='.') {
				sb.append('.'); // para fazer um new Double(str) o separador é '.'
				if(posLastDecSep>=0){
					sb.deleteCharAt(posLastDecSep);// para manter somente o último separador decimal a direita
				}
				posLastDecSep = sb.length()-1;
			}
		}
		if(sb.length()==0 || (sb.length()==1 && (sb.charAt(0)=='-' || sb.charAt(0)=='.')) ) {
			return "0";
		}
		//System.out.println("ZZZ>>>>"+sb);
		return sb.toString();
	}
	public Boolean getBoolean(String s){
		if(s==null) return null;
		String pattern = getPattern(Boolean.class);
		if(pattern!=null && pattern.indexOf(SEPARATOR_PATTERN_BOOLEAN)>0){ // o pattern para boolean será true/false (Sim/Não, V/F ...) 
			String[] p = pattern.split(SEPARATOR_PATTERN_BOOLEAN);
			if(p[0].equalsIgnoreCase(s)) return true;
			else if(p[1].equalsIgnoreCase(s)) return false; 
		}
		return new Boolean(s);
	}
	public long prepareStrToTimeLong(String value, Class<?> type){
		try {
			return new SimpleDateFormat(getPattern(type)).parse(value).getTime();
		} catch (ParseException e) {
			throw new APIBaseRuntimeException(e);
		}
	}

	
	

	public String getPattern(Class<?> type) { // recebe o tipo para retornar um pattern default para cada tipo
		if(java.sql.Timestamp.class.isAssignableFrom(type)) return getProp(PATTERN_DATE_TIME);
		if(java.sql.Time.class.isAssignableFrom(type)) return getProp(PATTERN_TIME);
		if(java.sql.Date.class.isAssignableFrom(type)) return getProp(PATTERN_DATE);
		if(java.util.Date.class.isAssignableFrom(type)) return getProp(PATTERN_DATE_TIME);
		if(java.lang.Boolean.class.isAssignableFrom(type)) return getProp(PATTERN_BOOLEAN);
		
		throw new IllegalArgumentException("Invalid type for pattern null: "+type.getName());			
	}




	
	
}




/*
// ver group separator !!! EX: 1.000,00
// ver remoção das casas decimais se getMinimumFractionDigits() for zero valor for int ?
// * usa fromatação própria porque o DecimalFormat usa o decimal separtor do Locale por cima do setado (df.getDecimalFormatSymbols().setDecimalSeparator(?))
public String formatDecimal(Object obj){// o obj será um object Double, Float ...
	Character decSep = getProp(DECIMAL_SEPARATOR);
	String sepStr = ""+decSep;
	StringBuilder sb = new StringBuilder(obj.toString().replace('.',decSep));
	if(sb.indexOf("E-")>0) {
		// ??? 
		return "0";// EX: pode ser 2.8421709430404007E-14
	}
	if(sb.indexOf(sepStr)<0) {
		sb.append(decSep);
	}
	int minInt = getProp(MINIMUM_INTEGER_DIGITS);
	while(sb.indexOf(sepStr)<minInt){
		sb.insert(0,'0');
	}
	int minFr = getProp(MINIMUM_FRACTION_DIGITS);
	int maxFr = getProp(MAXIMUM_FRACTION_DIGITS);
	int posSep = sb.indexOf(sepStr);
	if(sb.length()-1>posSep+maxFr){
		sb.delete(posSep+maxFr+1, sb.length());
	}else{
		while(sb.length()-minFr<=posSep){
			sb.append('0');
		}
	}
	
	// group separator
	int grSize = getProp(GROUPING_SIZE);
	if(grSize>0){
		int qtdDecimais = 0;
		posSep = sb.indexOf(sepStr);
		if(posSep>=0){
			qtdDecimais = sb.length() -posSep -sepStr.length();
		}
		char grSep = getProp(GROUPING_SEPARATOR);
		int iniLeft = sb.length()-(qtdDecimais>0?qtdDecimais+1:0);
		for(int z=iniLeft; z>grSize; z-=grSize){
			sb.insert(z-grSize,grSep);
		}
	}
	
	return sb.toString();
}*/